﻿using System;
using System.Collections.Generic;
using JelleDruyts.Windows.ObjectModel;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace JelleDruyts.Windows.Test
{
    [TestClass]
    public class ObservableObjectTest
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ObservableObjectShouldThrowWhenGettingPropertyWithNullName()
        {
            var sut = new MyObservableObject();
            sut.GetPropertyWithNullProperty();
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void ObservableObjectShouldThrowWhenSettingPropertyWithNullName()
        {
            var sut = new MyObservableObject();
            sut.SetPropertyWithNullProperty();
        }

        [TestMethod]
        public void ObservableObjectShouldReturnPropertyValues()
        {
            var sut = new MyObservableObject();
            Assert.IsFalse(sut.MyFlag);
            Assert.IsNull(sut.MyMessage);
            Assert.AreEqual<bool>(MyObservableObject.MyFlagWithDefaultProperty.DefaultValue, sut.MyFlagWithDefault);
            Assert.AreEqual<string>(MyObservableObject.MyMessageWithDefaultProperty.DefaultValue, sut.MyMessageWithDefault);

            sut.MyFlag = true;
            Assert.IsTrue(sut.MyFlag);
            sut.MyMessage = "Test";
            sut.MyFlagWithDefault = false;
            sut.MyMessageWithDefault = "New message";

            Assert.IsTrue(sut.MyFlag);
            Assert.AreEqual<string>(sut.MyMessage, "Test");
            Assert.IsFalse(sut.MyFlagWithDefault);
            Assert.AreEqual<string>(sut.MyMessageWithDefault, "New message");
        }

        [TestMethod]
        public void ObservableObjectShouldRaisePropertyChanged()
        {
            var sut = new MyObservableObject();
            var listener = new PropertyChangedListener(sut);

            sut.MyFlag = false;
            Assert.AreEqual<int>(0, listener.RaisedPropertyChangedEventArgs.Count);
            sut.MyFlag = true;
            Assert.AreEqual<int>(1, listener.RaisedPropertyChangedEventArgs.Count);
            Assert.AreEqual<string>("MyFlag", listener.RaisedPropertyChangedEventArgs[0].PropertyName);
        }

        [TestMethod]
        public void ObservableObjectShouldNotRaisePropertyChangedWhenSettingToDefaultValue()
        {
            var sut = new MyObservableObject();
            var listener = new PropertyChangedListener(sut);

            sut.MyFlag = false;
            sut.MyFlagWithDefault = MyObservableObject.MyFlagWithDefaultProperty.DefaultValue;
            sut.MyMessage = null;
            sut.MyMessageWithDefault = MyObservableObject.MyMessageWithDefaultProperty.DefaultValue;
            Assert.AreEqual<int>(0, listener.RaisedPropertyChangedEventArgs.Count);

            // Set the same values again to make sure no event is raised.
            sut.MyFlag = false;
            sut.MyFlagWithDefault = MyObservableObject.MyFlagWithDefaultProperty.DefaultValue;
            sut.MyMessage = null;
            sut.MyMessageWithDefault = MyObservableObject.MyMessageWithDefaultProperty.DefaultValue;
            Assert.AreEqual<int>(0, listener.RaisedPropertyChangedEventArgs.Count);
        }

        [TestMethod]
        public void ObservableObjectShouldRaisePropertyChangedWhenSettingToNonDefaultValue()
        {
            var sut = new MyObservableObject();
            var listener = new PropertyChangedListener(sut);

            // Set non-default values.
            sut.MyFlag = true;
            sut.MyFlagWithDefault = false;
            sut.MyMessage = "Test";
            sut.MyMessageWithDefault = "New message";
            Assert.AreEqual<int>(4, listener.RaisedPropertyChangedEventArgs.Count);
            Assert.AreEqual<string>("MyFlag", listener.RaisedPropertyChangedEventArgs[0].PropertyName);
            Assert.AreEqual<string>("MyFlagWithDefault", listener.RaisedPropertyChangedEventArgs[1].PropertyName);
            Assert.AreEqual<string>("MyMessage", listener.RaisedPropertyChangedEventArgs[2].PropertyName);
            Assert.AreEqual<string>("MyMessageWithDefault", listener.RaisedPropertyChangedEventArgs[3].PropertyName);

            // Set the same values again to make sure no event is raised.
            sut.MyFlag = true;
            sut.MyFlagWithDefault = false;
            sut.MyMessage = "Test";
            sut.MyMessageWithDefault = "New message";
            Assert.AreEqual<int>(4, listener.RaisedPropertyChangedEventArgs.Count);

            // Set new values again to make sure event is raised.
            sut.MyFlag = false;
            sut.MyFlagWithDefault = true;
            sut.MyMessage = "Test 2";
            sut.MyMessageWithDefault = "New message 2";
            Assert.AreEqual<int>(8, listener.RaisedPropertyChangedEventArgs.Count);
            Assert.AreEqual<string>("MyFlag", listener.RaisedPropertyChangedEventArgs[4].PropertyName);
            Assert.AreEqual<string>("MyFlagWithDefault", listener.RaisedPropertyChangedEventArgs[5].PropertyName);
            Assert.AreEqual<string>("MyMessage", listener.RaisedPropertyChangedEventArgs[6].PropertyName);
            Assert.AreEqual<string>("MyMessageWithDefault", listener.RaisedPropertyChangedEventArgs[7].PropertyName);
        }

        [TestMethod]
        public void ObservableObjectShouldCallObservablePropertyChangedCallback()
        {
            var sut = new MyObservableObject();
            Assert.AreEqual<int>(0, sut.RaisedObservablePropertyChangedEventArgs.Count);
            sut.MyMessageWithCallback = "New message";
            Assert.AreEqual<int>(1, sut.RaisedObservablePropertyChangedEventArgs.Count);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ObservablePropertyShouldThrowOnMethodLambda()
        {
            var prop = new ObservableProperty<string, MyObservableObject>(o => o.SomeMethod());
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ObservablePropertyShouldThrowOnFieldLambda()
        {
            var prop = new ObservableProperty<string, MyObservableObject>(o => o.SomeField);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ObservablePropertyShouldThrowOnWrongTypeLambda()
        {
            var prop = new ObservableProperty<string, MyObservableObject>(o => o.GetType().Name);
        }

        private class MyObservableObject : ObservableObject
        {
            public IList<ObservablePropertyChangedEventArgs<string>> RaisedObservablePropertyChangedEventArgs { get; private set; }

            public MyObservableObject()
            {
                this.RaisedObservablePropertyChangedEventArgs = new List<ObservablePropertyChangedEventArgs<string>>();
            }

            public bool MyFlag
            {
                get { return this.GetValue(MyFlagProperty); }
                set { this.SetValue(MyFlagProperty, value); }
            }

            public static ObservableProperty<bool> MyFlagProperty = new ObservableProperty<bool, MyObservableObject>(o => o.MyFlag);

            public bool MyFlagWithDefault
            {
                get { return this.GetValue<bool>(MyFlagWithDefaultProperty); }
                set { this.SetValue<bool>(MyFlagWithDefaultProperty, value); }
            }

            public static ObservableProperty<bool> MyFlagWithDefaultProperty = new ObservableProperty<bool, MyObservableObject>(o => o.MyFlagWithDefault, true);

            public string MyMessage
            {
                get { return this.GetValue<string>(MyMessageProperty); }
                set { this.SetValue(MyMessageProperty, value); }
            }

            public static ObservableProperty<string> MyMessageProperty = new ObservableProperty<string, MyObservableObject>(o => o.MyMessage);

            public string MyMessageWithDefault
            {
                get { return this.GetValue<string>(MyMessageWithDefaultProperty); }
                set { this.SetValue(MyMessageWithDefaultProperty, value); }
            }

            public static ObservableProperty<string> MyMessageWithDefaultProperty = new ObservableProperty<string, MyObservableObject>(o => o.MyMessageWithDefault, "Default message");

            public string MyMessageWithCallback
            {
                get { return this.GetValue(MyMessageWithCallbackProperty); }
                set { this.SetValue(MyMessageWithCallbackProperty, value); }
            }

            public static ObservableProperty<string> MyMessageWithCallbackProperty = new ObservableProperty<string, MyObservableObject>(o => o.MyMessageWithCallback, OnMyMessageWithCallbackChanged);

            private static void OnMyMessageWithCallbackChanged(ObservableObject sender, ObservablePropertyChangedEventArgs<string> e)
            {
                var myObject = (MyObservableObject)sender;
                myObject.RaisedObservablePropertyChangedEventArgs.Add(e);
            }

            public void GetPropertyWithNullProperty()
            {
                this.GetValue<string>(null);
            }

            public void SetPropertyWithNullProperty()
            {
                this.SetValue<string>(null, "dummy");
            }

            public string SomeMethod()
            {
                return null;
            }

            public string SomeField = "Some Value";
        }
    }
}